﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace EvoBoost.Core
{
	[Serializable]
	public class Tree<T>
	{
		private readonly List<TreeRegion<T>> treeRegions = new List<TreeRegion<T>>();
		
		private readonly ReadOnlyCollection<TreeRegion<T>> treeRegionsReadOnly;
		
		internal Tree(TreeNode<T> treeRoot, SplitBase<T> split)
		{
			Debug.Assert(treeRoot != null);
			Debug.Assert(split != null);

			this.treeRegionsReadOnly = new ReadOnlyCollection<TreeRegion<T>>(this.treeRegions);

			this.TraverseTree(treeRoot, split, new Stack<SplitRule<T>>());
		}

		internal Tree(double outcome)
		{
			this.treeRegionsReadOnly = new ReadOnlyCollection<TreeRegion<T>>(this.treeRegions);
			this.treeRegions.Add(new TreeRegion<T>(new List<SplitRule<T>>(),outcome));
		}

		public ReadOnlyCollection<TreeRegion<T>> TreeRegions
		{
			get { return this.treeRegionsReadOnly; }
		}

		private void TraverseTree(
			TreeNode<T> node,
			SplitBase<T> split,
			Stack<SplitRule<T>> splitRuleStack)
		{
			Debug.Assert(node != null && splitRuleStack != null && split != null);
			
			bool ruleAdded = false;
			if (node.Parent != null)
			{
				Debug.Assert(node.Parent.Type == TreeNodeType.Splitter && node.Parent.SplitParameters != null);
				
				ruleAdded = true;
				SplitRuleSign sign = node.Parent.LeftChild == node ? SplitRuleSign.LessOrEqual : SplitRuleSign.Greater;
				IEnumerable<double> splitParameters = node.Parent.SplitParameters;
				double threshold = node.Parent.ThresholdOrOutcome;
				splitRuleStack.Push(new SplitRule<T>(split, splitParameters, threshold, sign));
			}

			if (node.Type == TreeNodeType.Splitter)
			{
				this.TraverseTree(node.LeftChild, split, splitRuleStack);
				this.TraverseTree(node.RightChild, split, splitRuleStack);
			}
			else if (node.Type == TreeNodeType.Terminal)
			{
				this.treeRegions.Add(new TreeRegion<T>(splitRuleStack, node.ThresholdOrOutcome));
			}
			else
				Debug.Fail("We should never get there.");

			if (ruleAdded)
				splitRuleStack.Pop();
		}

		public double CalculateOutcome(T properties)
		{
			int coverCount = 0;
			double result = 0;
			foreach (TreeRegion<T> region in this.treeRegions)
			{
				if (region.CoversObject(properties))
				{
					coverCount += 1;
					result = region.Outcome;
#if !DEBUG
					return result;
#endif
				}
			}

			Debug.Assert(coverCount == 1);
			return result;
		}

		public override string ToString()
		{
			string result = String.Empty;
			for (int i = 0; i < treeRegions.Count; ++i)
			{
				result += treeRegions[i];
				if (i != treeRegions.Count - 1)
					result += Environment.NewLine;
			}

			return result;
		}
	}
}
